// matrix_implementation.h
#ifndef MATRIX_IMPLEMENTATION_H
#define MATRIX_IMPLEMENTATION_H

#include <ostream>
#include "matrix_exception.h"
#include "matrix_element_trait.h"
#include "reference_counter.h"

/**
 * MatrixImplementation is where the elements and dimension of a matrix are stored.
 * I have chosen to use a raw pointer to store the elements in an array.
 * I considered using a unique_ptr instead, but discarded the idea to get a challenge.
 * (Also there might be a slight decrease in memory usage since the 'deleter' of unique_ptr
 * must take up some memory). The public interface is quite similar to that of Matrix.
 * Instead of using operators, member functions are used.
 *
 * The type arguments T and ElementTrait are equivalent to those of Matrix.
 */
template<typename T, typename ElementTrait = MatrixElementTrait<T> >
class MatrixImplementation : public ReferenceCounter
{
private:
    // The dimension of the matrix (number of rows and columns)
    size_t _dim;
    // Pointer to memory where the elements of the matrix are stored
    T * _d;

    // Verify dimension is legal (not 0)
    static void checkDim(size_t dim) throw (MatrixException);
    // Verify dimension are equal
    static void checkDim(MatrixImplementation<T,ElementTrait> const & a, MatrixImplementation<T,ElementTrait> const & b) throw (MatrixException);
    // Verify divider is not too close to 0, based on ElementTrait::Precision
    static void checkDivider(T x) throw (MatrixException);
    // Invert matrix through raw pointers (same as in the assignment)
    static void inv_mat(const T *input, T *a, T *b, size_t n) throw (MatrixException);
    // Generate random matrix in raw pointer (same as in the assignment)
    static void gen_mat(T *a, size_t dim);
    // Multiply matrices through raw pointers (same as in the assignment)
    static void mat_mult(T const * a, T const * b, T * c, size_t dim);

public:
    explicit MatrixImplementation(size_t dim);
    // Copy the elements from one MatrixImplementation to another
    MatrixImplementation(MatrixImplementation<T,ElementTrait> const & toCopy);
    // Move elements from one MatrixImplementation to another
    MatrixImplementation(MatrixImplementation<T,ElementTrait> && toMove); 
    ~MatrixImplementation();

    bool IsIdentity() const;
    void Swap(MatrixImplementation<T,ElementTrait> & other);
    MatrixImplementation<T,ElementTrait> const Invert() const throw (MatrixException);
    size_t const Dimension() const;
    T const * GetInternal() const;

    // Accessor operators
    T & operator() (size_t row, size_t col) throw (MatrixException);
    T const operator() (size_t row, size_t col) const throw (MatrixException);

    // Arithmetic functions
    // The '*To' functions modify the object while the others create new objects which are returned through move constructor
    MatrixImplementation<T,ElementTrait> & AddTo (MatrixImplementation<T,ElementTrait> const & other) throw (MatrixException);
    MatrixImplementation<T,ElementTrait> const Add (MatrixImplementation<T,ElementTrait> const & other) const throw (MatrixException);
    MatrixImplementation<T,ElementTrait> & MultiplyTo (MatrixImplementation<T,ElementTrait> const & other) throw (MatrixException);
    MatrixImplementation<T,ElementTrait> const Multiply (MatrixImplementation<T,ElementTrait> const & other) const throw (MatrixException);
    
    // Equality function
	bool Equals (MatrixImplementation<T,ElementTrait> const & other) const;

    // Arithmetic operations with scalars
    template<typename U>
    MatrixImplementation<T,ElementTrait> & MultiplyTo(U const & other);
    template<typename U>
    MatrixImplementation<T,ElementTrait> const Multiply (U const & other) const;
    
    static MatrixImplementation<T,ElementTrait> Identity(size_t dim);
    static MatrixImplementation<T,ElementTrait> RandomMatrix(size_t dim);
};


#include "matrix_implementation_definition.h"

#endif // MATRIX_IMPLEMENTATION_H
